package evolutionaryballoons.behavior.genetic;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;

import evolutionaryballoons.Balloon;
import evolutionaryballoons.Configuration;
import evolutionaryballoons.Currents;
import evolutionaryballoons.Points;

public class Generations {
	private Random random;
	private BreedingGrounds breedingGrounds;
	private Programmer programmer = new Programmer();
	
	private Points points;
	private Currents currents;
	
	private List<Program> programs;
	
	private int countNodes(Node node) {
		int i = 1;
		
		if (node instanceof MultipleNode) {
			MultipleNode m = (MultipleNode) node;
			if (m.leftNode != null) 
				i += countNodes(m.leftNode);
			
			if (m.rightNode != null)
				i += countNodes(m.rightNode);
		}
		
		return i;
	}
	
	private Program create(Node rootNode) {
		Points pointsCopy = new Points(points);
		Balloon balloon = new Balloon(currents, pointsCopy);
		
		BalloonSimulation sim = new BalloonSimulation(balloon, 
				pointsCopy, new GeneticBehavior(balloon, pointsCopy, currents, rootNode));
		
		while (sim.isActive() && sim.tick < Configuration.sSimulateMaxTick) {
			sim.step();
		}
		
		// Create Program
		
		Program program = new Program();
		program.rootNode = rootNode;		
		program.pointsCollected = sim.getCollected();
		program.spentTicks = sim.tick;
		program.numberOfNodes = countNodes(rootNode);
		program.cost = sim.getCost();
		
		return program;
	}
	
	private Map<Integer, Program> createMapping(List<Program> programs) {
		Map<Integer, Program> mapping = new HashMap<Integer, Program>();
		
		int index = 0;
		for (Program program: programs) {
			for (int i = 0; i < Math.max(program.pointsCollected, 1); i++) {
				mapping.put(index, program);
				index++;
			}
		}
		
		System.out.println(mapping.size());
		
		return mapping;
	}
	
	private Program fromMapping(Map<Integer, Program> mapping) {
		int index = random.nextInt(mapping.size());
		
		return mapping.get(index);
	}
	
	private void populate() {
		List<Program> originals = new ArrayList<Program>(programs);
		Map<Integer, Program> mapping = createMapping(originals);
		
		programs.clear();
		
		while (programs.size() < Configuration.sGenerationSize) {
			Program parent1 = fromMapping(mapping);
			Program parent2 = fromMapping(mapping);
			
			Node rootNode = 
				breedingGrounds.createOffspring(parent1.rootNode, parent2.rootNode);
			
			programs.add(create(rootNode));
			
			if (programs.size() % 100 == 0) {
				System.out.print(programs.size() + ", ");
			}
		}
		
		Collections.sort(programs);
		
		System.out.println(String.format("[%s points, %s ticks, %s nodes, cost %s]", 
				programs.get(0).pointsCollected,
				programs.get(0).spentTicks,
				programs.get(0).numberOfNodes,
				programs.get(0).cost));
	}
	
	private List<Program> generate() {
		programs = new LinkedList<Program>();
		
		for (int i = 0; i < Configuration.sGenerationSize; i++) {
			Node rootNode = programmer.buildRandomTree(Configuration.sMultipleNodesSoftLimitStartup);
			programs.add(create(rootNode));
			
			if (i % (Configuration.sGenerationSize / 100) == 0) {
				System.out.println(i + "/" + Configuration.sGenerationSize);
				System.out.flush();
			}
		}
		
		return programs;
	}
	
	public Generations(Points points, Currents currents) {
		this.points = points;
		this.currents = currents;
		
		breedingGrounds = new BreedingGrounds();
		programmer = new Programmer();
		random = new Random();
	}
	
	public Node breedNode() {
		List<Program> programs = generate();
		
		for (int i = 0; i < Configuration.sNumberOfGenerations; i++) {
			System.out.println("Generation " + i);
			populate();
		}
		
		Collections.sort(programs);
		Node finalNode = programs.get(0).rootNode;
		
		Programmer.log(finalNode, 0);
		
		return finalNode;
	}
}
